<?php
/**
 * KumbiaPHP web & app Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://wiki.kumbiaphp.com/Licencia
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@kumbiaphp.com so we can send you a copy immediately.
 *
 * ActiveRecordBase Clase para el Mapeo Objeto Relacional
 *
 * @category   Kumbia
 * @package    Db
 * @subpackage ActiveRecord
 * @copyright  Copyright (c) 2005-2009 Kumbia Team (http://www.kumbiaphp.com)
 * @license    http://wiki.kumbiaphp.com/Licencia     New BSD License
 */
/**
 * ActiveRecordException
 */
require_once CORE_PATH . 'libs/db/active_record_base/exception.php';
/**
 * ActiveRecordBase Clase para el Mapeo Objeto Relacional
 *
 * Active Record es un enfoque al problema de acceder a los datos de una
 * base de datos en forma orientada a objetos. Una fila en la
 * tabla de la base de datos (o vista) se envuelve en una clase,
 * de manera que se asocian filas &uacute;nicas de la base de datos
 * con objetos del lenguaje de programaci&oacute;n usado.
 * Cuando se crea uno de estos objetos, se a&ntilde;de una fila a
 * la tabla de la base de datos. Cuando se modifican los atributos del
 * objeto, se actualiza la fila de la base de datos.
 *
 * Propiedades Soportadas:
 * $db = Conexion al Motor de Base de datos
 * $database = Base de datos a la que se conecta, especificada en databases.ini
 * $source = Tabla que contiene la tabla que esta siendo mapeada
 * $fields = Listado de Campos de la tabla que han sido mapeados
 * $count = Conteo del ultimo Resultado de un Select
 * $primary_key = Listado de columnas que conforman la llave primaria
 * $non_primary = Listado de columnas que no son llave primaria
 * $not_null = Listado de campos que son not_null
 * $attributes_names = nombres de todos los campos que han sido mapeados
 * $debug = Indica si se deben mostrar los SQL enviados al RDBM en pantalla
 * $logger = Si es diferente de false crea un log utilizando la clase Logger
 * en library/kumbia/logger/logger.php, esta crea un archivo .txt en logs/ con todas las
 * operaciones realizadas en ActiveRecord, si $logger = "nombre", crea un
 * archivo con ese nombre
 *
 * Propiedades sin Soportar:
 * $dynamic_update : La idea es que en un futuro ActiveRecord solo
 * actualize los campos que han cambiado.  (En Desarrollo)
 * $dynamic_insert : Indica si los valores del insert son solo aquellos
 * que sean no nulos. (En Desarrollo)
 * $select_before_update: Exige realizar una sentencia SELECT anterior
 * a la actualizacion UPDATE para comprobar que los datos no hayan sido
 * cambiados (En Desarrollo)
 * $subselect : Permitira crear una entidad ActiveRecord de solo lectura que
 * mapearia los resultados de un select directamente a un Objeto (En Desarrollo)
 *
 */
class ActiveRecordBase
{
	//Soportados

	/**
	 * Resource de conexion a la base de datos
	 *
	 * @var DbBase
	 */
	public $db;
	/**
	 * Base de datos a la que se conecta
	 *
	 * @var string
	 */
	protected $database;
	/**
	 * Schema donde esta la tabla
	 *
	 * @var string
	 */
	protected $schema;
	/**
	 * Tabla utilizada para realizar el mapeo
	 *
	 * @var string
	 */
	protected $source;
	/**
	 * Numero de resultados generados en la ultima consulta
	 *
	 * @var integer
	 */
	public $count;
	/**
	 * Nombres de los atributos de la entidad
	 *
	 * @var array
	 */
	public $fields = array();
	/**
	 * LLaves primarias de la entidad
	 *
	 * @var array
	 */
	public $primary_key = array();
	/**
	 * Campos que no son llave primaria
	 *
	 * @var array
	 */
	public $non_primary = array();
	/**
	 * Campos que no permiten nulos
	 *
	 * @var array
	 */
	public $not_null = array();
	/**
	 * Campos que tienen valor por defecto
	 *
	 * @var array
	 */
	protected $_with_default = array();
	/**
	 * Nombres de atributos, es lo mismo que fields
	 *
	 * @var array
	 */
	public $attributes_names = array();
	/**
	 * Indica si la clase corresponde a un mapeo de una vista
	 * en la base de datos
	 *
	 * @var boolean
	 */
	public $is_view = false;
	/**
	 * Indica si el modelo esta en modo debug
	 *
	 * @var boolean
	 */
	public $debug = false;
	/**
	 * Indica si se logearan los mensajes generados por la clase
	 *
	 * @var mixed
	 */
	public $logger = false;
	/**
	 * Indica si los datos del modelo deben ser persistidos
	 *
	 * @var boolean
	 */
	public $persistent = false;
	/**
	 * Validaciones
	 *
	 * inclusion_in: el campo pertenece a un conjunto de elementos
	 * exclusion_of: el campo no pertenece a un conjunto de elementos
	 * numericality_of: el campo debe ser numerico
	 * format_of: el campo debe coincidir con la expresion regular compatible con perl
	 * date_in: el campo debe ser una fecha valida
	 * email_in: el campo debe ser un correo electronico
	 * uniqueness_of: el campo debe ser unico
	 *
	 * @var array
	 **/
	protected $_validates = array('inclusion_in' => array(), 'exclusion_of' => array(), 'numericality_of' => array(),
		'format_of' => array(), 'date_in' => array(), 'email_in' => array(), 'uniqueness_of' => array());
	/**
	 * Campos que terminan en _in
	 *
	 * @var array
	 */
	protected $_in = array();
	/**
	 * Campos que terminan en _at
	 *
	 * @var array
	 */
	protected $_at = array();
	/**
	 * Variable para crear una condicion basada en los
	 * valores del where
	 *
	 * @var string
	 */
	protected $_where_pk;
	/**
	 * Indica si ya se han obtenido los metadatos del Modelo
	 *
	 * @var boolean
	 */
	protected $_dumped = false;
	/**
	 * Indica si hay bloqueo sobre los warnings cuando una propiedad
	 * del modelo no esta definida-
	 *
	 * @var boolean
	 */
	protected $_dump_lock = false;
	/**
	 * Tipos de datos de los campos del modelo
	 *
	 * @var array
	 */
	protected $_data_type = array();
	/**
	 * Relaciones a las cuales tiene una cardinalidad 1-1
	 *
	 * @var array
	 */
	protected $_has_one = array();
	/**
	 * Relaciones a las cuales tiene una cardinalidad 1-n
	 *
	 * @var array
	 */
	protected $_has_many = array();
	/**
	 * Relaciones a las cuales tiene una cardinalidad 1-1
	 *
	 * @var array
	 */
	protected $_belongs_to = array();
	/**
	 * Relaciones a las cuales tiene una cardinalidad n-n (muchos a muchos) o 1-n inversa
	 *
	 * @var array
	 */
	protected $_has_and_belongs_to_many = array();
	/**
	 * Clases de las cuales es padre la clase actual
	 *
	 * @var array
	 */
	public $parent_of = array();
	/**
	 * Persistance Models Meta-data
	 */
	protected static $_models = array();
	/**
	 * Persistance Models Meta-data
	 */
	public static $models = array();
	/**
	 * Constructor del Modelo
	 *
	 * @param array $data
	 */
	function __construct($data=null)
	{
		if (!$this->source) {
			$this->_model_name();
		}

		/**
		 * Inicializa el modelo en caso de que exista initialize
		 */
		if (method_exists($this, 'initialize')) {
			$this->initialize();
		}

		/**
		 * Conecta a la bd
		 **/
		$this->_connect();

		if ($data) {
			if (!is_array($data))
			$data = Util::getParams(func_get_args());
			$this->dump_result_self($data);
		}
	}
	/**
	 * Obtiene el nombre de la relacion en el RDBM a partir del nombre de la clase
	 *
	 */
	protected function _model_name()
	{
		if (!$this->source) {
			$this->source = Util::uncamelize(get_class($this));
		}
	}
	/**
	 * Establece publicamente el $source de la tabla
	 *
	 * @param string $source
	 */
	public function set_source($source)
	{
		$this->source = $source;
	}
	/**
	 * Devuelve el source actual
	 *
	 * @return string
	 */
	public function get_source()
	{
		return $this->source;
	}
	/**
	 * Establece la base datos a utilizar
	 *
	 * @param string $databse
	 */
	public function set_database($database)
	{
		$this->database = $database;
	}
	/**
	 * Devuelve la base de datos
	 *
	 * @return string
	 */
	public function get_database()
	{
		if ($this->database) {
			return $this->database;
		} else {
			$core = Config::read('config');
			return $core['application']['database'];
		}
	}
	/**
	 * Pregunta si el ActiveRecord ya ha consultado la informacion de metadatos
	 * de la base de datos o del registro persistente
	 *
	 * @return boolean
	 */
	public function is_dumped()
	{
		return $this->_dumped;
	}
	/**
	 * Valida que los valores que sean leidos del objeto ActiveRecord esten definidos
	 * previamente o sean atributos de la entidad
	 *
	 * @param string $property
	 */
	function __get($property)
	{
		if (!$this->_dump_lock) {
			if (!isset($this->$property)) {
				if (array_key_exists($property, $this->_belongs_to)) {
					$relation = $this->_belongs_to[$property];
					return self::get($relation->model)->find_first($this->{$relation->fk});
				} elseif (array_key_exists($property, $this->_has_one)) {
					$relation = $this->_has_one[$property];
					if ($this->{$this->primary_key[0]}) {
						return self::get($relation->model)->find_first("{$relation->fk}={$this->db->add_quotes($this->{$this->primary_key[0]}) }");
						} else {
							return null;
						}
					} elseif (array_key_exists($property, $this->_has_many)) {
						$relation = $this->_has_many[$property];
						if ($this->{$this->primary_key[0]}) {
							return self::get($relation->model)->find("{$relation->fk}={$this->db->add_quotes($this->{$this->primary_key[0]}) }");
							} else {
								return array();
							}
						} elseif (array_key_exists($property, $this->_has_and_belongs_to_many)) {
							$relation = $this->_has_and_belongs_to_many[$property];
							$relation_model = self::get($relation->model);
							$relation_model->dump_model();
							$source = $this->source;
							$relation_source = $relation_model->source;
							/**
							 * Cargo atraves de que tabla se efectuara la relacion
							 *
							 */
							if (!isset($relation->through)) {
								if ($source > $relation_source) {
									$relation->through = "{$this->source}_{$relation_source}";
								} else {
									$relation->through = "{$relation_source}_{$this->source}";
								}
							}
							if ($this->{$this->primary_key[0]}) {
								return self::get($relation->model)->find_all_by_sql("SELECT $relation_source.* FROM $relation_source, {$relation->through}, $source
							WHERE {$relation->through}.{$relation->key} = {$this->db->add_quotes($this->{$this->primary_key[0]}) }
							AND {$relation->through}.{$relation->fk} = $relation_source.{$relation_model->primary_key[0]}
							AND {$relation->through}.{$relation->key} = $source.{$this->primary_key[0]}
							ORDER BY $relation_source.{$relation_model->primary_key[0]}");
							} else {
								return array();
							}
							} else {
								return null;
							}
						}
					}
					return $this->$property;
				}
				/**
				 * Valida que los valores que sean asignados al objeto ActiveRecord esten definidos
				 * o sean atributos de la entidad
				 *
				 * @param string $property
				 * @param mixed $value
				 */
				function __set($property, $value)
				{
					if (!$this->_dump_lock) {
						if (!isset($this->$property) && is_object($value) && is_subclass_of($value, 'ActiveRecordBase')) {
							if (array_key_exists($property, $this->_belongs_to)) {
								$relation = $this->_belongs_to[$property];
								$value->dump_model();
								$this->{$relation->fk} = $value->{$value->primary_key[0]};
							} elseif (array_key_exists($property, $this->_has_one)) {
								$relation = $this->_has_one[$property];
								$value->{$relation->fk} = $this->{$this->primary_key[0]};
							}
						} elseif ($property == "source") {
							$value = ActiveRecord::sql_item_sanizite($value);
						}
					}
					$this->$property = $value;
				}
				/**
				 * Devuelve un valor o un listado dependiendo del tipo de Relaci&oacute;n
				 *
				 */
				public function __call($method, $args = array())
				{
					$has_relation = false;
					if (substr($method, 0, 8) == "find_by_") {
						$field = substr($method, 8);
						ActiveRecord::sql_item_sanizite($field);
						if (isset($args[0])) {
							$arg = array("conditions: $field = {$this->db->add_quotes($args[0]) }");
							unset($args[0]);
						} else {
							$arg = array();
						}
						return call_user_func_array(array($this, "find_first"), array_merge($arg, $args));
					}
					if (substr($method, 0, 9) == "count_by_") {
						$field = substr($method, 9);
						ActiveRecord::sql_item_sanizite($field);
						if (isset($args[0])) {
							$arg = array("conditions: $field = {$this->db->add_quotes($args[0]) }");
							unset($args[0]);
						} else {
							$arg = array();
						}
						return call_user_func_array(array($this, "count"), array_merge($arg, $args));
					}
					if (substr($method, 0, 12) == "find_all_by_") {
						$field = substr($method, 12);
						ActiveRecord::sql_item_sanizite($field);
						if (isset($args[0])) {
							$arg = array("conditions: $field = {$this->db->add_quotes($args[0]) }");
							unset($args[0]);
						} else {
							$arg = array();
						}
						return call_user_func_array(array($this, "find"), array_merge($arg, $args));
					}
					$model = ereg_replace("^get", "", $method);
					$mmodel = Util::uncamelize($model);
					if (array_key_exists($mmodel, $this->_belongs_to)) {
						$has_relation = true;
						$relation = $this->_belongs_to[$mmodel];
						return self::get($relation->model)->find_first($this->{$relation->fk});
					}
					if (array_key_exists($mmodel, $this->_has_many)) {
						$has_relation = true;
						$relation = $this->_has_many[$mmodel];
						if ($this->{$this->primary_key[0]}) {
							return self::get($relation->model)->find("{$relation->fk}={$this->db->add_quotes($this->{$this->primary_key[0]}) }");
							} else {
								return array();
							}
						}
						if (array_key_exists($mmodel, $this->_has_one)) {
							$has_relation = true;
							$relation = $this->_has_one[$mmodel];
							if ($this->{$this->primary_key[0]}) {
								return self::get($relation->model)->find_first("{$relation->fk}={$this->db->add_quotes($this->{$this->primary_key[0]}) }");
								} else {
									return null;
								}
							}
							if (array_key_exists($mmodel, $this->_has_and_belongs_to_many)) {
								$has_relation = true;
								$relation = $this->_has_and_belongs_to_many[$mmodel];
								if ($this->{$this->primary_key[0]}) {
									$source = $this->source;
									$relation_model = self::get($relation->model);
									$relation_model->dump_model();
									$relation_source = $relation_model->source;
									/**
									 * Cargo atraves de que tabla se efectuara la relacion
									 *
									 */
									if (!isset($relation->through)) {
										if ($source > $relation_source) {
											$relation->through = "{$this->source}_{$relation_source}";
										} else {
											$relation->through = "{$relation_source}_{$this->source}";
										}
									}
									return self::get($relation->model)->find_all_by_sql("SELECT $relation_source.* FROM $relation_source, {$relation->through}, $source
					WHERE {$relation->through}.{$relation->key} = {$this->db->add_quotes($this->{$this->primary_key[0]}) }
					AND {$relation->through}.{$relation->fk} = $relation_source.{$relation_model->primary_key[0]}
					AND {$relation->through}.{$relation->key} = $source.{$this->primary_key[0]}
					ORDER BY $relation_source.{$relation_model->primary_key[0]}");
					} else {
						return array();
					}
								}
								try {
									if (method_exists($this, $method)) {
										call_user_func_array(array($this, $method), $args);
									} else {
										if ($has_relation) {
											throw new ActiveRecordException("No existe el modelo '$model' para relacionar con ActiveRecord::{$this->source}");
										} else {
											throw new ActiveRecordException("No existe el m&eacute;todo '$method' en ActiveRecord::" . get_class($this));
										}
									}
								}
								catch(Exception $e) {
									$this->exceptions($e);
								}
								return $this->$method($args);
							}
							/**
							 * Se conecta a la base de datos y descarga los meta-datos si es necesario
							 *
							 * @param boolean $new_connection
							 */
							protected function _connect($new_connection = false)
							{
								if (!is_object($this->db) || $new_connection) {
									if ($this->database) {
										$this->db = DbBase::raw_connect($new_connection, $this->database);
									} else {
										$this->db = DbBase::raw_connect($new_connection);
									}
								}
								$this->db->debug = $this->debug;
								$this->db->logger = $this->logger;
								$this->dump();
							}
							/**
							 * Cargar los metadatos de la tabla
							 *
							 */
							public function dump_model()
							{
								$this->_connect();
							}
							/**
							 * Verifica si la tabla definida en $this->source existe
							 * en la base de datos y la vuelca en dump_info
							 *
							 * @return boolean
							 */
							protected function dump()
							{
								if ($this->_dumped) {
									return false;
								}
								$a = array();
								if ($this->source) {
									$this->source = str_replace(";", "", strtolower($this->source));
								} else {
									$this->_model_name();
									if (!$this->source) {
										return false;
									}
								}
								$table = $this->source;
								$schema = $this->schema;
								if (!count(self::get_meta_data($this->source))) {
									$this->_dumped = true;
									$this->_dump_info($table, $schema);
									if (!count($this->primary_key)) {
										if (!$this->is_view) {
											throw new ActiveRecordException("No se ha definido una llave primaria para la tabla '$table' esto imposibilita crear el ActiveRecord para esta entidad");
											return false;
										}
									}
								} else {
									if (!$this->is_dumped()) {
										$this->_dumped = true;
										$this->_dump_info($table, $schema);
									}
								}
								return true;
							}
							/**
							 * Vuelca la informaci&oacute;n de la tabla $table en la base de datos
							 * para armar los atributos y meta-data del ActiveRecord
							 *
							 * @param string $table
							 * @return boolean
							 */
							protected function _dump_info($table, $schema = '')
							{
								$this->_dump_lock = true;
								if (!count(self::get_meta_data($table))) {
									$meta_data = $this->db->describe_table($table, $schema);
									if ($meta_data) {
										self::set_meta_data($table, $meta_data);
									}
								}
								foreach(self::get_meta_data($table) as $field) {
									$this->fields[] = $field['Field'];
									if ($field['Key'] == 'PRI') {
										$this->primary_key[] = $field['Field'];
									} else $this->non_primary[] = $field['Field'];
									/**
									 * Si se indica que no puede ser nulo, pero se indica un
									 * valor por defecto, entonces no se incluye en la lista, ya que
									 * al colocar un valor por defecto, el campo nunca sera nulo
									 *
									 */
									if ($field['Null'] == 'NO' && !(isset($field['Default']) && $field['Default'])) {
										$this->not_null[] = $field['Field'];
									}
									if (isset($field['Default']) && $field['Default']) {
										$this->_with_default[] = $field['Field'];
									}
									if ($field['Type']) {
										$this->_data_type[$field['Field']] = strtolower($field['Type']);
									}
									if (substr($field['Field'], strlen($field['Field']) - 3, 3) == '_at') {
										$this->_at[] = $field['Field'];
									}
									if (substr($field['Field'], strlen($field['Field']) - 3, 3) == '_in') {
										$this->_in[] = $field['Field'];
									}
								}
								$this->attributes_names = $this->fields;
								$this->_dump_lock = false;
								return true;
							}
							/**
							 * Commit a Transaction
							 *
							 * @return success
							 */
							public function commit()
							{
								return $this->db->commit();
							}
							/**
							 * Rollback a Transaction
							 *
							 * @return success
							 */
							public function rollback()
							{
								return $this->db->rollback();
							}
							/**
							 * Start a transaction in RDBM
							 *
							 * @return success
							 */
							public function begin()
							{
								$this->_connect(true);
								return $this->db->begin();
							}
							/**
							 * Find all records in this table using a SQL Statement
							 *
							 * @param string $sqlQuery
							 * @return ActiveRecord Cursor
							 */
							public function find_all_by_sql($sqlQuery)
							{
								$results = array();
								foreach($this->db->fetch_all($sqlQuery) as $result) {
									$results[] = $this->dump_result($result);
								}
								return $results;
							}
							/**
							 * Find a record in this table using a SQL Statement
							 *
							 * @param string $sqlQuery
							 * @return ActiveRecord Cursor
							 */
							public function find_by_sql($sqlQuery)
							{
								$row = $this->db->fetch_one($sqlQuery);
								if ($row !== false) {
									$this->dump_result_self($row);
									return $this->dump_result($row);
								} else {
									return false;
								}
							}
							/**
							 * Execute a SQL Statement directly
							 *
							 * @param string $sqlQuery
							 * @return int affected
							 */
							public function sql($sqlQuery)
							{
								return $this->db->query($sqlQuery);
							}
							/**
							 * Return Fist Record
							 *
							 * @param mixed $what
							 * @param boolean $debug
							 *
							 * Recibe los mismos parametros que find
							 *
							 * @return ActiveRecord Cursor
							 */
							public function find_first($what = '')
							{
								$what = Util::getParams(func_get_args());
								$select = "SELECT ";
								if (isset($what['columns'])) {
									$select.= ActiveRecord::sql_sanizite($what['columns']);
								} elseif (isset($what['distinct'])) {
									$select.= 'DISTINCT ';
									$select.= $what['distinct'] ? ActiveRecord::sql_sanizite($what['distinct']) : join(",", $this->fields);
								} else {
									$select.= join(",", $this->fields);
								}
								if ($this->schema) {
									$select.= " FROM {$this->schema}.{$this->source}";
								} else {
									$select.= " FROM {$this->source}";
								}
								$what['limit'] = 1;
								$select.= $this->convert_params_to_sql($what);
								$resp = false;
								try {
									$result = $this->db->fetch_one($select);
									if ($result) {
										$this->dump_result_self($result);
										$resp = $this->dump_result($result);
									}
								}
								catch(Exception $e) {
									$this->exceptions($e);
								}
								return $resp;
							}
							/**
							 * Find data on Relational Map table
							 *
							 * @param string $what
							 * @return ActiveRecord Cursor
							 *
							 * columns: columnas a utilizar
							 * conditions : condiciones de busqueda en WHERE
							 * join: inclusion inner join o outer join
							 * group : campo para grupo en GROUP BY
							 * having : condicion para el grupo
							 * order : campo para criterio de ordenamiento ORDER BY
							 * distinct: campos para hacer select distinct
							 */
							public function find($what = '')
							{
								$what = Util::getParams(func_get_args());
								$select = "SELECT ";
								if (isset($what['columns'])) {
									$select.= $what['columns'] ? ActiveRecord::sql_sanizite($what['columns']) : join(",", $this->fields);
								} elseif (isset($what['distinct'])) {
									$select.= 'DISTINCT ';
									$select.= $what['distinct'] ? ActiveRecord::sql_sanizite($what['distinct']) : join(",", $this->fields);
								} else {
									$select.= join(",", $this->fields);
								}
								if ($this->schema) {
									$select.= " FROM {$this->schema}.{$this->source}";
								} else {
									$select.= " FROM {$this->source}";
								}
								$select.= $this->convert_params_to_sql($what);
								$results = array();
								$all_results = $this->db->in_query($select);
								foreach($all_results AS $result) {
									$results[] = $this->dump_result($result);
								}
								$this->count = count($results);
								if (isset($what[0]) && is_numeric($what[0])) {
									if (!isset($results[0])) {
										$this->count = 0;
										return false;
									} else {
										$this->dump_result_self($all_results[0]);
										$this->count = 1;
										return $results[0];
									}
								} else {
									$this->count = count($results);
									return $results;
								}
							}
							/*
							 * Arma una consulta SQL con el parametro $what, así:
							 * 	$what = Util::getParams(func_get_args());
							 * 	$select = "SELECT * FROM Clientes";
							 *	$select.= $this->convert_params_to_sql($what);
							 *
							 * @param string $what
							 * @return string
							 */
							public function convert_params_to_sql($what = '')
							{
								$select = "";
								if (is_array($what)) {
									if (!isset($what['conditions'])) {
										if (!isset($this->primary_key[0]) && (isset($this->id) || $this->is_view)) {
											$this->primary_key[0] = "id";
										}
										ActiveRecord::sql_item_sanizite($this->primary_key[0]);
										if (isset($what[0])) {
											if (is_numeric($what[0])) {
												$what['conditions'] = "{$this->primary_key[0]} = {$this->db->add_quotes($what[0]) }";
											} else {
												if ($what[0] == '') {
													$what['conditions'] = "{$this->primary_key[0]} = ''";
												} else {
													$what['conditions'] = $what[0];
												}
											}
										}
									}
									if (isset($what['join']) && $what['join']) {
										$select.= " {$what['join']}";
									}
									if (isset($what['conditions']) && $what['conditions']) {
										$select.= " WHERE {$what['conditions']}";
									}
									if (isset($what['group']) && $what['group']) {
										$select.= " GROUP BY {$what['group']}";
									}
									if (isset($what['having']) && $what['having']) {
										$select.= " HAVING {$what['having']}";
									}
									if (isset($what['order']) && $what['order']) {
										ActiveRecord::sql_sanizite($what['order']);
										$select.= " ORDER BY {$what['order']}";
									}
									$limit_args = array($select);
									if (isset($what['limit'])) {
										array_push($limit_args, "limit: $what[limit]");
									}
									if (isset($what['offset'])) {
										array_push($limit_args, "offset: $what[offset]");
									}
									if (count($limit_args) > 1) {
										$select = call_user_func_array(array($this, 'limit'), $limit_args);
									}
								} else {
									if (strlen($what)) {
										if (is_numeric($what)) {
											$select.= "WHERE {$this->primary_key[0]} = '$what'";
										} else {
											$select.= "WHERE $what";
										}
									}
								}
								return $select;
							}
							/*
							 * Devuelve una clausula LIMIT adecuada al RDBMS empleado
							 *
							 * limit: maxima cantidad de elementos a mostrar
							 * offset: desde que elemento se comienza a mostrar
							 *
							 * @param string $sql consulta select
							 * @return String clausula LIMIT adecuada al RDBMS empleado
							 */
							public function limit($sql)
							{
								$args = func_get_args();
								return call_user_func_array(array($this->db, 'limit'), $args);
							}
							/**
							 * Ejecuta un SELECT DISTINCT
							 * @param string $what
							 * @return array
							 *
							 * Soporta parametros iguales a find
							 *
							 */
							public function distinct($what = '')
							{
								$what = Util::getParams(func_get_args());
								if ($this->schema) {
									$table = $this->schema . "." . $this->source;
								} else {
									$table = $this->source;
								}
								if (!isset($what['columns'])) {
									$what['columns'] = $what['0'];
								} else {
									if (!$what['columns']) {
										$what['columns'] = $what['0'];
									}
								}
								$what['columns'] = ActiveRecord::sql_sanizite($what['columns']);
								$select = "SELECT DISTINCT {$what['columns']} FROM $table ";
								/**
								 * Se elimina el de indice cero ya que por defecto convert_params_to_sql lo considera como una condicion en WHERE
								 */
								unset($what[0]);
								$select.= $this->convert_params_to_sql($what);
								$results = array();
								foreach($this->db->fetch_all($select) as $result) {
									$results[] = $result[0];
								}
								return $results;
							}
							/**
							 * Ejecuta una consulta en el RDBM directamente
							 *
							 * @param string $sql
							 * @return resource
							 */
							public function select_one($sql)
							{
								if (substr(ltrim($sql), 0, 7) != "SELECT") {
									$sql = "SELECT " . $sql;
								}
								$num = $this->db->fetch_one($sql);
								return $num[0];
							}
							static public function static_select_one($sql)
							{
								$db = db::raw_connect();
								if (substr(ltrim($sql), 0, 7) != "SELECT") {
									$sql = "SELECT " . $sql;
								}
								$num = $db->fetch_one($sql);
								return $num[0];
							}
							/**
							 * Realiza un conteo de filas
							 *
							 * @param string $what
							 * @return integer
							 */
							public function count($what = '')
							{
								$what = Util::getParams(func_get_args());
								if ($this->schema) {
									$table = "{$this->schema}.{$this->source}";
								} else {
									$table = $this->source;
								}
								if (isset($what['distinct']) && $what['distinct']) {
									if (isset($what['group']) || isset($what['order'])) {
										$select = "SELECT COUNT(*) FROM (SELECT DISTINCT {$what['distinct']} FROM $table ";
										$select.= $this->convert_params_to_sql($what);
										$select.= ') AS t ';
									} else {
										$select = "SELECT COUNT(DISTINCT {$what['distinct']}) FROM $table ";
										$select.= $this->convert_params_to_sql($what);
									}
								} else {
									$select = "SELECT COUNT(*) FROM $table ";
									$select.= $this->convert_params_to_sql($what);
								}
								$num = $this->db->fetch_one($select);
								return $num[0];
							}
							/**
							 * Realiza un promedio sobre el campo $what
							 *
							 * @param string $what
							 * @return array
							 */
							public function average($what = '')
							{
								$what = Util::getParams(func_get_args());
								if (isset($what['column'])) {
									if (!$what['column']) {
										$what['column'] = $what[0];
									}
								} else {
									$what['column'] = $what[0];
								}
								unset($what[0]);
								ActiveRecord::sql_item_sanizite($what['column']);
								if ($this->schema) {
									$table = "{$this->schema}.{$this->source}";
								} else {
									$table = $this->source;
								}
								$select = "SELECT AVG({$what['column']}) FROM $table ";
								$select.= $this->convert_params_to_sql($what);
								$num = $this->db->fetch_one($select);
								return $num[0];
							}
							public function sum($what = '')
							{
								$what = Util::getParams(func_get_args());
								if (isset($what['column'])) {
									if (!$what['column']) {
										$what['column'] = $what[0];
									}
								} else {
									$what['column'] = $what[0];
								}
								unset($what[0]);
								ActiveRecord::sql_item_sanizite($what['column']);
								if ($this->schema) {
									$table = "{$this->schema}.{$this->source}";
								} else {
									$table = $this->source;
								}
								$select = "SELECT SUM({$what['column']}) FROM $table ";
								$select.= $this->convert_params_to_sql($what);
								$num = $this->db->fetch_one($select);
								return $num[0];
							}
							/**
							 * Busca el valor maximo para el campo $what
							 *
							 * @param string $what
							 * @return mixed
							 */
							public function maximum($what = '')
							{
								$what = Util::getParams(func_get_args());
								if (isset($what['column'])) {
									if (!$what['column']) {
										$what['column'] = $what[0];
									}
								} else {
									$what['column'] = $what[0];
								}
								unset($what[0]);
								ActiveRecord::sql_item_sanizite($what['column']);
								if ($this->schema) {
									$table = "{$this->schema}.{$this->source}";
								} else {
									$table = $this->source;
								}
								$select = "SELECT MAX({$what['column']}) FROM $table ";
								$select.= $this->convert_params_to_sql($what);
								$num = $this->db->fetch_one($select);
								return $num[0];
							}
							/**
							 * Busca el valor minimo para el campo $what
							 *
							 * @param string $what
							 * @return mixed
							 */
							public function minimum($what = '')
							{
								$what = Util::getParams(func_get_args());
								if (isset($what['column'])) {
									if (!$what['column']) {
										$what['column'] = $what[0];
									}
								} else {
									$what['column'] = $what[0];
								}
								unset($what[0]);
								ActiveRecord::sql_item_sanizite($what['column']);
								if ($this->schema) {
									$table = "{$this->schema}.{$this->source}";
								} else {
									$table = $this->source;
								}
								$select = "SELECT MIN({$what['column']}) FROM $table ";
								$select.= $this->convert_params_to_sql($what);
								$num = $this->db->fetch_one($select);
								return $num[0];
							}
							/**
							 * Realiza un conteo directo mediante $sql
							 *
							 * @param string $sqlQuery
							 * @return mixed
							 */
							public function count_by_sql($sqlQuery)
							{
								$num = $this->db->fetch_one($sqlQuery);
								return $num[0];
							}
							/**
							 * Iguala los valores de un resultado de la base de datos
							 * en un nuevo objeto con sus correspondientes
							 * atributos de la clase
							 *
							 * @param array $result
							 * @return ActiveRecord
							 */
							public function dump_result($result){
								$obj = clone $this;
								/**
								 * Consulta si la clase es padre de otra y crea el tipo de dato correcto
								 */
								if (isset($result['type'])) {
									if (in_array($result['type'], $this->parent_of)) {
										if (class_exists($result['type'])) {
											$obj = new $result['type'];
											unset($result['type']);
										}
									}
								}
								$this->_dump_lock = true;
								if (is_array($result)) {
									foreach($result as $k => $r) {
										if (!is_numeric($k)) {
											$obj->$k = stripslashes($r);
										}
									}
								}
								$this->_dump_lock = false;
								return $obj;
							}
							/**
							 * Iguala los valores de un resultado de la base de datos
							 * con sus correspondientes atributos de la clase
							 *
							 * @param array $result
							 * @return ActiveRecord
							 */
							public function dump_result_self($result)
							{
								$this->_dump_lock = true;
								if (is_array($result)) {
									foreach($result as $k => $r) {
										if (!is_numeric($k)) {
											$this->$k = stripslashes($r);
										}
									}
								}
								$this->_dump_lock = false;
							}
							/**
							 * Create a new Row using values from $_REQUEST
							 *
							 * @param string $form form name for request, equivalent to $_REQUEST[$form]
							 * @return boolean success
							 */
							public function create_from_request($form = '')
							{
								if ($form) {
									return $this->create($_REQUEST[$form]);
								} else {
									return $this->create($_REQUEST);
								}
							}
							/**
							 * Saves a new Row using values from $_REQUEST
							 *
							 * @param string $form form name for request, equivalent to $_REQUEST[$form]
							 * @return boolean success
							 */
							public function save_from_request($form = '')
							{
								if ($form) {
									return $this->save($_REQUEST[$form]);
								} else {
									return $this->save($_REQUEST);
								}
							}
							/**
							 * Updates a Row using values from $_REQUEST
							 *
							 * @param string $form form name for request, equivalent to $_REQUEST[$form]
							 * @return boolean success
							 */
							public function update_from_request($form = '')
							{
								if ($form) {
									return $this->update($_REQUEST[$form]);
								} else {
									return $this->update($_REQUEST);
								}
							}
							/**
							 * Creates a new Row in map table
							 *
							 * @param mixed $values
							 * @return success boolean
							 */
							public function create()
							{
								if (func_num_args() > 0) {
									$params = Util::getParams(func_get_args());
									$values = (isset($params[0]) && is_array($params[0])) ? $params[0] : $params;
									foreach($this->fields as $field) {
										if (isset($values[$field])) {
											$this->$field = $values[$field];
										}
									}
								}
								if ($this->primary_key[0] == 'id') {
									$this->id = null;
								}
								return $this->save();
							}
							/**
							 * Consulta si un determinado registro existe o no
							 * en la entidad de la base de datos
							 *
							 * @return boolean
							 */
							function exists($where_pk = '')
							{
								if ($this->schema) {
									$table = "{$this->schema}.{$this->source}";
								} else {
									$table = $this->source;
								}
								if (!$where_pk) {
									$where_pk = array();
									foreach($this->primary_key as $key) {
										if ($this->$key) {
											$where_pk[] = " $key = '{$this->$key}'";
										}
									}
									if (count($where_pk)) {
										$this->_where_pk = join(" AND ", $where_pk);
									} else {
										return 0;
									}
									$query = "SELECT COUNT(*) FROM $table WHERE {$this->_where_pk}";
								} else {
									if (is_numeric($where_pk)) {
										$query = "SELECT(*) FROM $table WHERE id = '$where_pk'";
									} else {
										$query = "SELECT COUNT(*) FROM $table WHERE $where_pk";
									}
								}
								$num = $this->db->fetch_one($query);
								return $num[0];
							}
							/**
							 * Saves Information on the ActiveRecord Properties
							 * @param array $values array de valores a cargar
							 * @return boolean success
							 */
							public function save($values=null)
							{
								if ($values) {
									if(!is_array($values))
									$values = Util::getParams(func_get_args());
									foreach($this->fields as $field) {
										if (isset($values[$field])) {
											$this->$field = $values[$field];
										}
									}
								}
								$ex = $this->exists();
								if ($this->schema) {
									$table = $this->schema . "." . $this->source;
								} else {
									$table = $this->source;
								}
								#Run Validation Callbacks Before
								if (method_exists($this, 'before_validation')) {
									if ($this->before_validation() == 'cancel') {
										return false;
									}
								} else {
									if (isset($this->before_validation)) {
										$method = $this->before_validation;
										if ($this->$method() == 'cancel') {
											return false;
										}
									}
								}
								if(!$ex){
									if (method_exists($this, "before_validation_on_create")) {
										if ($this->before_validation_on_create() == 'cancel') {
											return false;
										}
									} else {
										if (isset($this->before_validation_on_create)) {
											$method = $this->before_validation_on_create;
											if ($this->$method() == 'cancel') {
												return false;
											}
										}
									}
								}
								if($ex){
									if (method_exists($this, "before_validation_on_update")) {
										if ($this->before_validation_on_update() == 'cancel') {
											return false;
										}
									} else {
										if (isset($this->before_validation_on_update)) {
											$method = $this->before_validation_on_update;
											if($this->$method() == 'cancel') {
												return false;
											}
										}
									}
								}

								/**
								 * Validacion validates_presence
								 *
								 */
								if(isset($this->_validates['presence_of'])) {
									foreach($this->_validates['presence_of'] as $f => $opt) {
										if (isset($this->$f) && (is_null($this->$f) || $this->$f == '')) {
											if (!$ex && $f == 'id')
											continue;
											if (isset($opt['message'])) {
												Flash::error($opt['message']);
												return false;
											} else {
												$field = isset($opt['field']) ? $opt['field'] : $f;
												Flash::error("Error: El campo $field no puede ser nulo");
												return false;
											}
										}
									}
								}

								/**
								 * Recordamos que aqui no aparecen los que tienen valores por defecto,
								 * pero sin embargo se debe estar pendiente de validar en las otras verificaciones
								 * los campos nulos, ya que en estas si el campo es nulo, realmente se refiere a un campo que
								 * debe tomar el valor por defecto
								 *
								 */
								foreach ($this->not_null as $f) {
									if (!isset($this->$f) || is_null($this->$f) || $this->$f == '') {
										if (!$ex && $f == 'id') {
											continue;
										}
										if (!$ex && in_array($f, $this->_at)) {
											continue;
										}
										if ($ex && in_array($f, $this->_in)) {
											continue;
										}
										Flash::error("Error: El campo $f no puede ser nulo");
										return false;
									}
								}

								/**
								 * Validacion validates_length
								 *
								 */
								if(isset($this->_validates['length_of'])) {
									foreach($this->_validates['length_of'] as $f => $opt) {
										if (isset($this->$f) && !is_null($this->$f) && $this->$f != '') {
											$field = isset($opt['field']) ? $opt['field'] : $f;

											if (strlen($this->$f) < $opt['min']) {
												if (isset($opt['too_short']))
												Flash::error($opt['too_short']);
												else
												Flash::error("Error: El campo $field debe tener como mínimo $opt[min] caracteres");
												return false;
											}

											if (strlen($this->$f) > $opt['max']) {
												if (isset($opt['too_long']))
												Flash::error($opt['too_long']);
												else
												Flash::error("Error: El campo $field debe tener como máximo $opt[max] caracteres");
												return false;
											}
										}
									}
								}

								/**
								 * Validacion validates_inclusion
								 *
								 */
								foreach($this->_validates['inclusion_in'] as $f => $opt) {
									if (isset($this->$f) && !is_null($this->$f) && $this->$f != '') {
										if (!in_array($this->$f, $opt['list'])) {
											if (isset($opt['message'])) {
												Flash::error($opt['message']);
											} else {
												$field = isset($opt['field']) ? $opt['field'] : $f;
												Flash::error("$field debe tener un valor entre (" . join(",", $opt['list']) . ")");
											}
											return false;
										}
									}
								}

								/**
								 * Validacion validates_exclusion
								 *
								 */
								foreach($this->_validates['exclusion_of'] as $f => $opt) {
									if (isset($this->$f) && !is_null($this->$f) && $this->$f != '') {
										if (in_array($this->$f, $opt['list'])) {
											if (isset($opt['message'])) {
												Flash::error($opt['message']);
											} else {
												$field = isset($opt['field']) ? $opt['field'] : $f;
												Flash::error("$field no debe tener un valor entre (" . join(",", $opt['list']) . ")");
											}
											return false;
										}
									}
								}

								/**
								 * Validacion validates_numericality
								 *
								 */
								foreach($this->_validates['numericality_of'] as $f => $opt) {
									if (isset($this->$f) && !is_null($this->$f) && $this->$f != '') {
										if (!is_numeric($this->$f)) {
											if (isset($opt['message'])) {
												Flash::error($opt['message']);
											} else {
												$field = isset($opt['field']) ? $opt['field'] : $f;
												Flash::error("$field debe tener un valor numérico");
											}
											return false;
										}
									}
								}

								/**
								 * Validacion validates_format
								 *
								 */
								foreach($this->_validates['format_of'] as $f => $opt) {
									if (isset($this->$f) && !is_null($this->$f) && $this->$f != '') {
										if (!filter_var($this->$f, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>$opt['pattern'])))) {
											if (isset($opt['message'])) {
												Flash::error($opt['message']);
											} else {
												$field = isset($opt['field']) ? $opt['field'] : $f;
												Flash::error("Formato erroneo para $field");
											}
											return false;
										}
									}
								}

								/**
								 * Validacion validates_date
								 *
								 */
								foreach($this->_validates['date_in'] as $f => $opt) {
									if (isset($this->$f) && !is_null($this->$f) && $this->$f != '') {
										if (!filter_var($this->$f, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>"/^\d{4}[-\/](0[1-9]|1[012])[-\/](0[1-9]|[12][0-9]|3[01])$/")))) {
											if (isset($opt['message'])) {
												Flash::error($opt['message']);
											} else {
												$field = isset($opt['field']) ? $opt['field'] : $f;
												Flash::error("Formato de fecha erroneo para $field");
											}
											return false;
										}
									}
								}

								/**
								 * Validacion validates_email
								 *
								 */
								foreach($this->_validates['email_in'] as $f=>$opt) {
									if (isset($this->$f) && !is_null($this->$f) && $this->$f != '') {
										if (!filter_var($this->$f, FILTER_VALIDATE_EMAIL)) {
											if (isset($opt['message'])) {
												Flash::error($opt['message']);
											} else {
												$field = isset($opt['field']) ? $opt['field'] : $f;
												Flash::error("Formato de e-mail erroneo en el campo $field");
											}
											return false;
										}
									}
								}

								/**
								 * Validacion validates_uniqueness
								 *
								 */
								foreach($this->_validates['uniqueness_of'] as $f => $opt) {
									if (isset($this->$f) && !is_null($this->$f) && $this->$f != '') {
										$result = $this->db->fetch_one("SELECT COUNT(*) FROM $table WHERE $f = {$this->db->add_quotes($this->$f)}");
										if ($result[0]) {
											if (isset($opt['message'])) {
												Flash::error($opt['message']);
											} else {
												$field = isset($opt['field']) ? $opt['field'] : $f;
												Flash::error("El valor '{$this->$f}' ya existe para el campo $field");
											}
											return false;
										}
									}
								}

								#Run Validation Callbacks After
								if(!$ex){
									if (method_exists($this, "after_validation_on_create")) {
										if ($this->after_validation_on_create() == 'cancel') {
											return false;
										}
									} else {
										if (isset($this->after_validation_on_create)) {
											$method = $this->after_validation_on_create;
											if ($this->$method() == 'cancel') {
												return false;
											}
										}
									}
								}
								if($ex){
									if (method_exists($this, "after_validation_on_update")) {
										if ($this->after_validation_on_update() == 'cancel') {
											return false;
										}
									} else {
										if (isset($this->after_validation_on_update)) {
											$method = $this->after_validation_on_update;
											if ($this->$method() == 'cancel') return false;
										}
									}
								}

								if (method_exists($this, 'after_validation')) {
									if ($this->after_validation() == 'cancel') {
										return false;
									}
								} else {
									if (isset($this->after_validation)) {
										$method = $this->after_validation;
										if ($this->$method() == 'cancel') {
											return false;
										}
									}
								}
								# Run Before Callbacks
								if (method_exists($this, "before_save")) {
									if ($this->before_save() == 'cancel') {
										return false;
									}
								} else {
									if (isset($this->before_save)) {
										$method = $this->before_save;
										if ($this->$method() == 'cancel') {
											return false;
										}
									}
								}
								if($ex){
									if (method_exists($this, "before_update")) {
										if ($this->before_update() == 'cancel') {
											return false;
										}
									} else {
										if (isset($this->before_update)) {
											$method = $this->before_update;
											if ($this->$method() == 'cancel') {
												return false;
											}
										}
									}
								}
								if(!$ex){
									if (method_exists($this, "before_create")) {
										if ($this->before_create() == 'cancel') {
											return false;
										}
									} else {
										if (isset($this->before_create)) {
											$method = $this->before_create;
											if ($this->$method() == 'cancel') {
												return false;
											}
										}
									}
								}
								$environment = Config::read('databases');
								$config = $environment[$this->get_database()];
								if ($ex) {
									$fields = array();
									$values = array();
									foreach($this->non_primary as $np) {
										$np = ActiveRecord::sql_item_sanizite($np);
										if (in_array($np, $this->_in)) {
											if ($config['type'] == 'oracle') {
												$this->$np = date("Y-m-d");
											} else {
												$this->$np = date("Y-m-d G:i:s");
											}
										}
										if (isset($this->$np)) {
											$fields[] = $np;
											if (is_null($this->$np) || $this->$np == '') {
												$values[] = "NULL";
											} elseif (substr($this->$np, 0, 1) == "%") {
												$values[] = str_replace("%", "", $this->$np);
											} else {
												/**
												 * Se debe especificar el formato de fecha en Oracle
												 */
												if ($this->_data_type[$np] == 'date' && $config['type'] == 'oracle') {
													$values[] = "TO_DATE(" . $this->db->add_quotes($this->$np) . ", 'YYYY-MM-DD')";
												} else {
													$values[] = $this->db->add_quotes($this->$np);
												}
											}
										}
									}
									$val = $this->db->update($table, $fields, $values, $this->_where_pk);
								} else {
									$fields = array();
									$values = array();
									foreach($this->fields as $field) {
										if ($field != 'id' && !$this->id) {
											if (in_array($field, $this->_at)) {
												if ($config['type'] == 'oracle') {
													$this->$field = date("Y-m-d");
												} else {
													$this->$field = date("Y-m-d G:i:s");
												}
											}
											if (in_array($field, $this->_in)) {
												unset($this->$field);
											}
											$use_default = in_array($field, $this->_with_default) && isset($this->$field) && (is_null($this->$field) || $this->$field == '');
											if($this->_data_type[$field] == 'datetime' && $config['type'] == 'mysql'){
												$this->$field = date("Y-m-d G:i:s",strtotime($this->$field));
											}
											if (isset($this->$field) && !$use_default) {
												$fields[] = ActiveRecord::sql_sanizite($field);
												if (substr($this->$field, 0, 1) == "%") {
													$values[] = str_replace("%", "", $this->$field);
												} else {
													if ($this->is_a_numeric_type($field) || $this->$field == null) {
														$values[] = addslashes($this->$field !== '' && $this->$field !== null ? $this->$field : "NULL");
													} else {
														if ($this->_data_type[$field] == 'date' && $config['type'] == 'oracle') {
															/**
															 * Se debe especificar el formato de fecha en Oracle
															 */
															$values[] = "TO_DATE(" . $this->db->add_quotes($this->$field) . ", 'YYYY-MM-DD')";
														} else {
															if (!is_null($this->$field) && $this->$field != '') {
																$values[] = $this->db->add_quotes($this->$field);
															} else {
																$values[] = "NULL";
															}
														}
													}
												}
											}
										} else {
											/**
											 * Campos autonumericos en Oracle deben utilizar una sequencia auxiliar
											 */
											if ($config['type'] == 'oracle') {
												if (!$this->id) {
													$fields[] = "id";
													$values[] = $this->source . "_id_seq.NEXTVAL";
												}
											}
											if ($config['type'] == 'informix') {
												if (!$this->id) {
													$fields[] = "id";
													$values[] = 0;
												}
											}
										}
									}
									$val = $this->db->insert($table, $values, $fields);
								}
								if (!isset($config['pdo']) && $config['type'] == 'oracle') {
									$this->commit();
								}
								if (!$ex) {
									//$this->db->logger = true;
									$m = $this->db->last_insert_id($table, $this->primary_key[0]);
									$this->find_first($m);
								}
								if ($val) {
									if($ex){
										if (method_exists($this, "after_update")) {
											if ($this->after_update() == 'cancel') {
												return false;
											}
										} else {
											if (isset($this->after_update)) {
												$method = $this->after_update;
												if ($this->$method() == 'cancel') {
													return false;
												}
											}
										}
									}
									if(!$ex){
										if (method_exists($this, "after_create")) {
											if ($this->after_create() == 'cancel') {
												return false;
											}
										} else {
											if (isset($this->after_create)) {
												$method = $this->after_create;
												if ($this->$method() == 'cancel') {
													return false;
												}
											}
										}
									}
									if (method_exists($this, "after_save")) {
										if ($this->after_save() == 'cancel') {
											return false;
										}
									} else {
										if (isset($this->after_save)) {
											$method = $this->after_save;
											if ($this->$method() == 'cancel') {
												return false;
											}
										}
									}
									return $val;
								} else {
									return false;
								}
							}
							/**
							 * Find All data in the Relational Table
							 *
							 * @param string $field
							 * @param string $value
							 * @return ActiveRecod Cursor
							 */
							function find_all_by($field, $value)
							{
								ActiveRecord::sql_item_sanizite($field);
								return $this->find("conditions: $field = {$this->db->add_quotes($value) }");
							}
							/**
							 * Updates Data in the Relational Table
							 *
							 * @param mixed $values
							 * @return boolean sucess
							 */
							function update()
							{
								if (func_num_args() > 0) {
									$params = Util::getParams(func_get_args());
									$values = (isset($params[0]) && is_array($params[0])) ? $params[0] : $params;
									foreach($this->fields as $field) {
										if (isset($values[$field])) {
											$this->$field = $values[$field];
										}
									}
								}
								if ($this->exists()) {
									if (method_exists($this, 'before_change')) {
										$obj = clone $this;
										if($this->before_change($obj->find($this->id)) == 'cancel'){
											return false;
										}
										unset($obj);
									}
									if($this->save()){
										if (method_exists($this, 'after_change')) {
											if ($this->after_change($this) == 'cancel') {
												return false;
											}
										}
										return true;
									}
								} else {
									Flash::error('No se puede actualizar porque el registro no existe');
									return false;
								}
							}
							/**
							 * Deletes data from Relational Map Table
							 *
							 * @param mixed $what
							 */
							public function delete($what = '')
							{
								if (func_num_args() > 1) {
									$what = Util::getParams(func_get_args());
								}
								if ($this->schema) {
									$table = $this->schema . "." . $this->source;
								} else {
									$table = $this->source;
								}
								$conditions = "";
								if (is_array($what)) {
									if ($what["conditions"]) {
										$conditions = $what["conditions"];
									}
								} else {
									if (is_numeric($what)) {
										ActiveRecord::sql_sanizite($this->primary_key[0]);
										$conditions = "{$this->primary_key[0]} = '$what'";
									} else {
										if ($what) {
											$conditions = $what;
										} else {
											ActiveRecord::sql_sanizite($this->primary_key[0]);
											$conditions = "{$this->primary_key[0]} = '{$this->{$this->primary_key[0]}}'";
											}
										}
									}
									if (method_exists($this, "before_delete")) {
										if ($this->id) {
											$this->find($this->id);
										}
										if ($this->before_delete() == 'cancel') {
											return false;
										}
									} else {
										if (isset($this->before_delete)) {
											if ($this->id) {
												$this->find($this->id);
											}
											$method = $this->before_delete;
											if ($this->$method() == 'cancel') {
												return false;
											}
										}
									}
									$val = $this->db->delete($table, $conditions);
									if ($val) {
										if (method_exists($this, "after_delete")) {
											if ($this->after_delete() == 'cancel') {
												return false;
											}
										} else {
											if (isset($this->after_delete)) {
												$method = $this->after_delete;
												if ($this->$method() == 'cancel') {
													return false;
												}
											}
										}
									}
									return $val;
								}
								/**
								 * Actualiza todos los atributos de la entidad
								 * $Clientes->update_all("estado='A', fecha='2005-02-02'", "id>100");
								 * $Clientes->update_all("estado='A', fecha='2005-02-02'", "id>100", "limit: 10");
								 *
								 * @param string $values
								 */
								public function update_all($values) {
									$params = array();
									if ($this->schema) {
										$table = $this->schema . "." . $this->source;
									} else {
										$table = $this->source;
									}
									if (func_num_args() > 1) {
										$params = Util::getParams(func_get_args());
									}
									if (!isset($params['conditions']) || !$params['conditions']) {
										if (isset($params[1])) {
											$params['conditions'] = $params[1];
										} else {
											$params['conditions'] = "";
										}
									}
									if ($params['conditions']) {
										$params['conditions'] = " WHERE " . $params['conditions'];
									}
									$sql = "UPDATE $table SET $values {$params['conditions']}";
									$limit_args = array($sql);
									if (isset($params['limit'])) {
										array_push($limit_args, "limit: $params[limit]");
									}
									if (isset($params['offset'])) {
										array_push($limit_args, "offset: $params[offset]");
									}
									if (count($limit_args) > 1) {
										$sql = call_user_func_array(array($this, 'limit'), $limit_args);
									}
									$environment = Config::read('databases');
									$config = $environment[$this->get_database()];
									if (!isset($config->pdo) || !$config->pdo) {
										if ($config['type'] == "informix") {
											$this->db->set_return_rows(false);
										}
									}
									return $this->db->query($sql);
								}
								/**
								 * Delete All data from Relational Map Table
								 *
								 * @param string $conditions
								 * @return boolean
								 */
								public function delete_all($conditions = '') {
									$limit = "";
									if ($this->schema) {
										$table = $this->schema . "." . $this->source;
									} else {
										$table = $this->source;
									}
									if (func_num_args() > 1) {
										$params = Util::getParams(func_get_args());
										$limit_args = array($select);
										if (isset($params['limit'])) {
											array_push($limit_args, "limit: $params[limit]");
										}
										if (isset($params['offset'])) {
											array_push($limit_args, "offset: $params[offset]");
										}
										if (count($limit_args) > 1) {
											$select = call_user_func_array(array($this, 'limit'), $limit_args);
										}
									}
									return $this->db->delete($table, $conditions);
								}
								/**
								 * *********************************************************************************
								 * Metodos de Debug
								 * *********************************************************************************
								 */
								/**
								 * Imprime una version humana de los valores de los campos
								 * del modelo en una sola linea
								 *
								 */
								public function inspect() {
									$inspect = array();
									foreach($this->fields as $field) {
										if (!is_array($field)) {
											$inspect[] = "$field: {$this->$field}";
										}
									}
									return join(", ", $inspect);
								}
								/**
								 * *********************************************************************************
								 * Metodos de Validacion
								 * *********************************************************************************
								 */
								/**
								 * Valida que el campo no sea nulo
								 *
								 * @param string $field campo a validar
								 * @param array $params parametros adicionales
								 *
								 * message: mensaje a mostrar
								 * field: nombre de campo a mostrar en el mensaje
								 */
								protected function validates_presence_of($field, $params=array())
								{
									if (is_string($params))
									$params = Util::getParams(func_get_args());

									$this->_validates['presence_of'][$field] = $params;
								}
								/**
								 * Valida el tamañoo de ciertos campos antes de insertar
								 * o actualizar
								 *
								 * @params string $field campo a validar
								 * @param int $max valor maximo
								 * @param int $min valor minimo
								 * @param array $params parametros adicionales
								 *
								 * too_short: mensaje a mostrar cuando se muy corto
								 * too_long: mensaje a mostrar cuando sea muy largo
								 * field: nombre de campo a mostrar en el mensaje
								 */
								protected function validates_length_of($field, $max, $min=0, $params=array())
								{
									if (is_string($params))
									$params = Util::getParams(func_get_args());

									$this->_validates['length_of'][$field] = $params;
									$this->_validates['length_of'][$field]['min'] = $min;
									$this->_validates['length_of'][$field]['max'] = $max;
								}
								/**
								 * Valida que el campo se encuentre entre los valores de una lista
								 * antes de insertar o actualizar
								 *
								 * @param string $field campo a validar
								 * @param array $list
								 *
								 * message: mensaje a mostrar
								 * field: nombre del campo a mostrar en el mensaje
								 */
								protected function validates_inclusion_in($field, $list, $params=array())
								{
									if (is_string($params))
									$params = Util::getParams(func_get_args());

									$this->_validates['inclusion_in'][$field] = $params;
									$this->_validates['inclusion_in'][$field]['list'] = $list;
								}
								/**
								 * Valida que el campo no se encuentre entre los valores de una lista
								 * antes de insertar o actualizar
								 *
								 * @param string $field campo a validar
								 * @param array $list
								 *
								 * message: mensaje a mostrar
								 * field: nombre del campo a mostrar en el mensaje
								 */
								protected function validates_exclusion_of($field, $list, $params=array())
								{
									if (is_string($params))
									$params = Util::getParams(func_get_args());

									$this->_validates['exclusion_of'][$field] = $params;
									$this->_validates['exclusion_of'][$field]['list'] = $list;
								}
								/**
								 * Valida que el campo tenga determinado formato segun una expresion regular
								 * antes de insertar o actualizar
								 *
								 * @param string $field campo a validar
								 * @param string $pattern expresion regular para preg_match
								 *
								 * message: mensaje a mostrar
								 * field: nombre del campo a mostrar en el mensaje
								 */
								protected function validates_format_of($field, $pattern, $params=array())
								{
									if (is_string($params))
									$params = Util::getParams(func_get_args());

									$this->_validates['format_of'][$field] = $params;
									$this->_validates['format_of'][$field]['pattern'] = $pattern;
								}
								/**
								 * Valida que ciertos atributos tengan un valor numerico
								 * antes de insertar o actualizar
								 *
								 * @param string $field campo a validar
								 *
								 * message: mensaje a mostrar
								 * field: nombre del campo a mostrar en el mensaje
								 */
								protected function validates_numericality_of($field, $params=array())
								{
									if (is_string($params))
									$params = Util::getParams(func_get_args());

									$this->_validates['numericality_of'][$field] = $params;
								}
								/**
								 * Valida que ciertos atributos tengan un formato de e-mail correcto
								 * antes de insertar o actualizar
								 *
								 * @param string $field campo a validar
								 *
								 * message: mensaje a mostrar
								 * field: nombre del campo a mostrar en el mensaje
								 */
								protected function validates_email_in($field, $params=array())
								{
									if (is_string($params))
									$params = Util::getParams(func_get_args());

									$this->_validates['email_in'][$field] = $params;
								}
								/**
								 * Valida que ciertos atributos tengan un valor unico antes
								 * de insertar o actualizar
								 *
								 * @param string $field campo a validar
								 *
								 * message: mensaje a mostrar
								 * field: nombre del campo a mostrar en el mensaje
								 */
								protected function validates_uniqueness_of($field, $params=array())
								{
									if (is_string($params))
									$params = Util::getParams(func_get_args());

									$this->_validates['uniqueness_of'][$field] = $params;
								}
								/**
								 * Valida que ciertos atributos tengan un formato de fecha acorde al indicado en
								 * config/config.ini antes de insertar o actualizar
								 *
								 * @param string $field campo a validar
								 *
								 * message: mensaje a mostrar
								 * field: nombre del campo a mostrar en el mensaje
								 */
								protected function validates_date_in($field, $params=array())
								{
									if (is_string($params))
									$params = Util::getParams(func_get_args());

									$this->_validates['date_in'][$field] = $params;
								}
								/**
								 * Verifica si un campo es de tipo de dato numerico o no
								 *
								 * @param string $field
								 * @return boolean
								 */
								public function is_a_numeric_type($field)
								{
									if (strpos(" " . $this->_data_type[$field], "int") || strpos(" " . $this->_data_type[$field], "decimal") || strpos(" " . $this->_data_type[$field], "number")) {
										return true;
									} else {
										return false;
									}
								}
								/**
								 * Obtiene los datos de los metadatos generados por Primera vez en la Sesi&oacute;n
								 *
								 * @param string $table
								 * @return array
								 */
								static function get_meta_data($table)
								{
									if (isset(self::$models[$table])) {
										return self::$models[$table];
									} elseif(PRODUCTION && ($metadata = Cache::get($table, 'kumbia.models'))) {
										self::$models[$table] = unserialize($metadata);
										return $metadata;
									}
									return array();
								}
								/**
								 * Crea un registro de meta datos para la tabla especificada
								 *
								 * @param string $table
								 * @param array $meta_data
								 */
								static function set_meta_data($table, $meta_data)
								{
									if(PRODUCTION) {
										Cache::save(serialize($meta_data), Config::get('config.application.metadata_lifetime'), $table, 'kumbia.models');
									}
									self::$models[$table] = $meta_data;
									return true;
								}
								/*******************************************************************************************
								 * Metodos para generacion de relaciones
								 *******************************************************************************************/
								/**
								 * Crea una relacion 1-1 entre dos modelos
								 *
								 * @param string $relation
								 *
								 * model : nombre del modelo al que se refiere
								 * fk : campo por el cual se relaciona (llave foranea)
								 */
								protected function has_one($relation)
								{
									$params = Util::getParams(func_get_args());
									for ($i = 0;isset($params[$i]);$i++) {
										$relation = Util::uncamelize($params[$i]);
										if (!array_key_exists($relation, $this->_has_one)) {
											$this->_has_one[$relation] = new stdClass();
											$this->_has_one[$relation]->model = isset($params['model']) ? $params['model'] : $relation;
											$this->_has_one[$relation]->fk = isset($params['fk']) ? $params['fk'] : Util::uncamelize(get_class($this)) . '_id';
										}
									}
								}
								/**
								 * Crea una relacion 1-1 inversa entre dos modelos
								 *
								 * @param string $relation
								 *
								 * model : nombre del modelo al que se refiere
								 * fk : campo por el cual se relaciona (llave foranea)
								 */
								protected function belongs_to($relation)
								{
									$params = Util::getParams(func_get_args());
									for ($i = 0;isset($params[$i]);$i++) {
										$relation = Util::uncamelize($params[$i]);
										if (!array_key_exists($relation, $this->_belongs_to)) {
											$this->_belongs_to[$relation] = new stdClass();
											$this->_belongs_to[$relation]->model = isset($params['model']) ? $params['model'] : $relation;
											$this->_belongs_to[$relation]->fk = isset($params['fk']) ? $params['fk'] : "{$relation}_id";
										}
									}
								}
								/**
								 * Crea una relacion 1-n entre dos modelos
								 *
								 * @param string $relation
								 *
								 * model : nombre del modelo al que se refiere
								 * fk : campo por el cual se relaciona (llave foranea)
								 */
								protected function has_many($relation)
								{
									$params = Util::getParams(func_get_args());
									for ($i = 0;isset($params[$i]);$i++) {
										$relation = Util::uncamelize($params[$i]);
										if (!array_key_exists($relation, $this->_has_many)) {
											$this->_has_many[$relation] = new stdClass();
											$this->_has_many[$relation]->model = isset($params['model']) ? $params['model'] : $relation;
											$this->_has_many[$relation]->fk = isset($params['fk']) ? $params['fk'] : Util::uncamelize(get_class($this)) . '_id';
										}
									}
								}
								/**
								 * Crea una relacion n-n o 1-n inversa entre dos modelos
								 *
								 * @param string $relation
								 *
								 * model : nombre del modelo al que se refiere
								 * fk : campo por el cual se relaciona (llave foranea)
								 * key: campo llave que identifica al propio modelo
								 * through : atrav�s de que tabla
								 */
								protected function has_and_belongs_to_many($relation)
								{
									$params = Util::getParams(func_get_args());
									for ($i = 0;isset($params[$i]);$i++) {
										$relation = Util::uncamelize($params[$i]);
										if (!array_key_exists($relation, $this->_has_and_belongs_to_many)) {
											$this->_has_and_belongs_to_many[$relation] = new stdClass();
											$this->_has_and_belongs_to_many[$relation]->model = isset($params['model']) ? $params['model'] : $relation;
											$this->_has_and_belongs_to_many[$relation]->fk = isset($params['fk']) ? $params['fk'] : "{$relation}_id";
											$this->_has_and_belongs_to_many[$relation]->key = isset($params['key']) ? $params['key'] : Util::uncamelize(get_class($this)) . '_id';
											if (isset($params['through'])) {
												$this->_has_and_belongs_to_many[$relation]->through = $params['through'];
											}
										}
									}
								}
								/**
								 * Herencia Simple
								 */
								/**
								 * Especifica que la clase es padre de otra
								 *
								 * @param string $parent
								 */
								public function parent_of($parent)
								{
									$parents = func_get_args();
									foreach($parents as $parent) {
										if (!in_array($parent, $this->parent_of)) {
											$this->parent_of[] = $parent;
										}
									}
								}
								/**
								 * Elimina caracteres que podrian ayudar a ejecutar
								 * un ataque de Inyeccion SQL
								 *
								 * @param string $sql_item
								 */
								public static function sql_item_sanizite($sql_item)
								{
									$sql_item = trim($sql_item);
									if ($sql_item !== '' && $sql_item !== null) {
										$sql_item = ereg_replace("[ ]+", "", $sql_item);
										if (!ereg("^[a-zA-Z0-9_\.]+$", $sql_item)) {
											throw new ActiveRecordException("Se esta tratando de ejecutar una operacion maliciosa!");
										}
									}
									return $sql_item;
								}
								/**
								 * Elimina caracteres que podrian ayudar a ejecutar
								 * un ataque de Inyeccion SQL
								 *
								 * @param string $sql_item
								 */
								public static function sql_sanizite($sql_item)
								{
									$sql_item = trim($sql_item);
									if ($sql_item !== '' && $sql_item !== null) {
										$sql_item = ereg_replace("[ ]+", "", $sql_item);
										if (!ereg("^[a-zA-Z_0-9\,\(\)\.\*]+$", $sql_item)) {
											throw new ActiveRecordException("Se esta tratando de ejecutar una operacion maliciosa!");
										}
									}
									return $sql_item;
								}
								/**
								 * Al sobreescribir este metodo se puede controlar las excepciones de un modelo
								 *
								 * @param unknown_type $e
								 */
								protected function exceptions($e)
								{
									throw $e;
								}
								/**
								 * Implementacion de __toString Standard
								 *
								 */
								public function __toString()
								{
									return "<" . get_class() . " Object>";
								}
								/**
								 * Paginador para el modelo
								 *
								 * conditions: condiciones para paginacion
								 * page: numero de pagina a mostrar (por defecto la pagina 1)
								 * per_page: cantidad de elementos por pagina (por defecto 10 items por pagina)
								 *
								 * @return un objeto Page identico al que se regresa con el util paginate
								 *
								 */
								public function paginate()
								{
									$args = func_get_args();
									array_unshift($args, $this->source);
									if(!class_exists('Paginator')){
										require CORE_PATH . 'libs/db/behaviors/paginate.php';
									}
									return call_user_func_array(array('Paginator' , 'paginate'), $args);
								}
								/**
								 * Paginador para el modelo atraves de consulta sql
								 *
								 * @param string $sql consulta sql
								 *
								 * page: numero de pagina a mostrar (por defecto la pagina 1)
								 * per_page: cantidad de elementos por pagina (por defecto 10 items por pagina)
								 *
								 * @return un objeto Page identico al que se regresa con el util paginate_by_sql
								 *
								 */
								public function paginate_by_sql($sql)
								{
									$args = func_get_args();
									array_unshift($args, $this->source);
									if(!class_exists('Paginator')){
										require CORE_PATH . 'libs/db/behaviors/paginate.php';
									}
									return call_user_func_array(array('Paginator' , 'paginate_by_sql'), $args);
								}
								/**
								 * Operaciones al serializar
								 *
								 **/
								public function __sleep()
								{
									/**
									 * Anulando conexion a bd en el modelo
									 **/
									$this->db = null;

									return array_keys(get_object_vars($this));
								}
								/**
								 * Operaciones al deserializar
								 *
								 **/
								public function __wakeup()
								{
									/**
									 * Restableciendo conexion a la bd
									 **/
									$this->_connect();
								}
								/**
								 * Obtiene la instacia de un modelo
								 *
								 * @param string $model
								 * @return ActiveRecord
								 * @throw KumbiaException
								 **/
								public static function get($model)
								{
									if(isset(self::$_models[$model])) {
										return self::$_models[$model];
									}

									/**
									 * Nombre de la clase
									 **/
									$Model = Util::camelcase(basename($model));

									/**
									 * Verifico si esta cargada la clase
									 **/
									if(!class_exists($Model, false)) {
										/**
										 * Carga la clase
										 **/
										$file = APP_PATH . "models/$model.php";
										if(is_file($file)) {
											include $file;
										} else {
											throw new KumbiaException("No existe el modelo ActiveRecord $model");
										}
									}

									self::$_models[$model] = $obj = new $Model();
									return $obj;
								}
							}
